require 'date'

# FIXME this is simplified copypasta of the Burndown class from sprint.rb
class ReleaseBurndown
  class Series < Array
    def initialize(*args)
      @is_trendline = args.pop
      @color = args.pop
      @datastring = args.pop
      @name = args.pop

      raise "Name '#{@name}' must be a symbol" unless @name.is_a?  Symbol
      super(*args)
    end

    attr_reader :name
    attr_reader :datastring
    attr_reader :color
    attr_reader :is_trendline
  end

  def initialize(release)
    @days = release.days
    @release_id = release.id

    # end date for graph
    days = @days
    daycount = days.size
    #days = release.days(Date.today) if release.release_end_date > Date.today

# woj hack start

    @release = release
    
    # make series of story points
    
    remaining_story_points_str = "["
    total_story_points_str = "["
    burned_story_points_str = "["
    ReleaseBurndownDay.find(:all, :order=>'day', :conditions => ["release_id = ?", release.id]).each { |bdd|
      remaining_story_points_str += "['#{bdd.day}', #{bdd.remaining_story_points}],"
      total_story_points_str += "['#{bdd.day}', #{bdd.total_story_points}],"
      burned_story_points_str += "['#{bdd.day}', #{bdd.burned_story_points}],"
    }
    remaining_story_points_str += "]"
    total_story_points_str += "]"
    burned_story_points_str += "]"
    
    make_series :remaining_story_points, remaining_story_points_str, "#EAA228", false
    make_series :total_story_points, total_story_points_str, "#c5b47f", false
    make_series :burned_story_points, burned_story_points_str, "#579575", false

    # make series of ideal line
    
    ideal_str = "[['#{release.release_start_date}', #{release.initial_story_points}], ['#{release.release_end_date}', 0]]"

    make_series :ideal, ideal_str, "#4bb2c5", false
    
    # avoid array errors
    
    start_day = release.release_start_date
    start_value = 0
    end_day = release.release_end_date
    start_day0 = release.release_start_date
    
    # trends and extrapolations

    if release.burndown_days.size > 0
      start_day = release.burndown_days[-1].day
      end_day = release.release_end_date
      start_day0 = release.burndown_days[0].day
    end
    
    # burned
 
    if release.burndown_days.size > 0
      start_value = release.burndown_days[-1].burned_story_points
    end
    color = "#579575"
    
    daily_increase = release.daily_points_burn_rate
    end_value = start_value + (end_day - start_day) * daily_increase
    start_value0 = start_value - (start_day - start_day0) * daily_increase;
      
    str = "[['#{start_day}', #{start_value}], ['#{end_day}', #{end_value}]]"
    make_series :extrapolation_burned, str, color, true
    str = "[['#{start_day0}', #{start_value0}], ['#{start_day}', #{start_value}]]"
    make_series :trend_burned, str, color, true

    # total
 
    if release.burndown_days.size > 0
      start_value = release.burndown_days[-1].total_story_points;
    end
    color = "#c5b47f"
    
    daily_increase = release.daily_backlog_grow_rate
    end_value = start_value + (end_day - start_day) * daily_increase
    start_value0 = start_value - (start_day - start_day0) * daily_increase;
      
    str = "[['#{start_day}', #{start_value}], ['#{end_day}', #{end_value}]]"
    make_series :extrapolation_total, str, color, true
    str = "[['#{start_day0}', #{start_value0}], ['#{start_day}', #{start_value}]]"
    make_series :trend_total, str, color, true

    # avoid division by 0
    
    if release.p10_factor == 0
      release.p10_factor = 1
    end
    if release.p50_factor == 0
      release.p10_factor = 1
    end
    if release.p90_factor == 0
      release.p10_factor = 1
    end
    
    # p10
    
    daily_increase = release.daily_points_burn_rate * (1 - 1/release.p10_factor)
    end_value = start_value + (end_day - start_day) * daily_increase
    start_value0 = start_value - (start_day - start_day0) * daily_increase;
      
    str = "[['#{start_day}', #{start_value}], ['#{end_day}', #{end_value}]]"
    make_series :extrapolation_total_p10, str, color, true
#    str = "[['#{start_day0}', #{start_value0}], ['#{start_day}', #{start_value}]]"
#    make_series :trend_total_p10, str, color, true

    # p50
    
    daily_increase = release.daily_points_burn_rate * (1 - 1/release.p50_factor)
    end_value = start_value + (end_day - start_day) * daily_increase
    start_value0 = start_value - (start_day - start_day0) * daily_increase;
      
    str = "[['#{start_day}', #{start_value}], ['#{end_day}', #{end_value}]]"
    make_series :extrapolation_total_p50, str, color, true
#    str = "[['#{start_day0}', #{start_value0}], ['#{start_day}', #{start_value}]]"
#    make_series :trend_total_p50, str, color, true

    # p90
    
    daily_increase = release.daily_points_burn_rate * (1 - 1/release.p90_factor)
    end_value = start_value + (end_day - start_day) * daily_increase
    start_value0 = start_value - (start_day - start_day0) * daily_increase;
      
    str = "[['#{start_day}', #{start_value}], ['#{end_day}', #{end_value}]]"
    make_series :extrapolation_total_p90, str, color, true
#    str = "[['#{start_day0}', #{start_value0}], ['#{start_day}', #{start_value}]]"
#    make_series :trend_total_p90, str, color, true

# woj hack end    
    
    @max = @available_series.values.flatten.compact.max
  end

  attr_reader :days
  attr_reader :release_id
  attr_reader :max

  attr_reader :remaining_story_points
  attr_reader :ideal

  def series(select = :active)
    if(@release.release_burndown_type == 0)
      return @available_series.values.select{|s|  s.name == :ideal || s.name == :remaining_story_points }.sort{|x,y| "#{x.name}" <=> "#{y.name}"}
    elsif(@release.release_burndown_type == 1)
      return @available_series.values.select{|s|  s.name == :ideal || s.name == :remaining_story_points || s.name == :total_story_points || s.name == :burned_story_points }.sort{|x,y| "#{x.name}" <=> "#{y.name}"}
    elsif(@release.release_burndown_type == 2)
      return @available_series.values.select{|s|  s.name == :ideal || s.name == :remaining_story_points || s.name == :total_story_points || s.name == :burned_story_points || s.name == :trend_burned || s.name == :trend_total }.sort{|x,y| "#{x.name}" <=> "#{y.name}"}
    elsif(@release.release_burndown_type == 3)
      return @available_series.values.select{|s|  s.name == :ideal || s.name == :remaining_story_points || s.name == :total_story_points || s.name == :burned_story_points || s.name == :trend_burned || s.name == :trend_total || s.name == :extrapolation_burned || s.name == :extrapolation_total }.sort{|x,y| "#{x.name}" <=> "#{y.name}"}
    elsif(@release.release_burndown_type == 4)
      return @available_series.values.select{|s|  s.name == :ideal || s.name == :remaining_story_points || s.name == :total_story_points || s.name == :burned_story_points || s.name == :trend_burned || s.name == :extrapolation_burned || s.name == :extrapolation_total_p10 || s.name == :extrapolation_total_p50 || s.name == :extrapolation_total_p90 }.sort{|x,y| "#{x.name}" <=> "#{y.name}"}
    end
    return @available_series.values.select{|s| (select == :all) }.sort{|x,y| "#{x.name}" <=> "#{y.name}"}
  end

  private

  def make_series(name, datastring, color, is_trendline)
    @available_series ||= {}
    s = ReleaseBurndown::Series.new([0], name, datastring, color, is_trendline)
    @available_series[name] = s
    instance_variable_set("@#{name}", s)
  end

  def to_h(keys, values)
    return Hash[*keys.zip(values).flatten]
  end

end

class Release < ActiveRecord::Base
    unloadable

    belongs_to :project
    has_many :release_burndown_days, :dependent => :delete_all

    validates_presence_of :project_id, :name, :release_start_date, :release_end_date, :initial_story_points
    validates_length_of :name, :maximum => 64
    validate :dates_valid?

    def dates_valid?
        if self.release_start_date and self.release_end_date
          errors.add_to_base(l(:error_release_end_after_start)) if self.release_start_date >= self.release_end_date
        end
    end

    # returns all sprints that overlap with this release
    def sprints
	conditions = ["(effective_date >= ?) and (sprint_start_date <= ?)", self.release_start_date, self.release_end_date]
	
        Sprint.find(:all, :conditions => conditions)
    end
    
    # woj: should not be used
    # use open_stories and closed_stories instead
    def stories
        return Story.product_backlog(@project)
    end

    # returns open stories from backlog and open stories from open sprints
    def open_stories
        s = Story.product_backlog(@project)
	sprints.each {|sprint| sprint.stories.each{ |story| s << story unless story.closed? } unless sprint.closed? }
        return s
    end
    
    # returns all stories from closed sprints and closed stories from open sprints
    def closed_stories
        s = []
	sprints.each {|sprint| sprint.closed? ? s+=sprint.stories : sprint.stories.each{ |story| s << story if story.closed? } }
        return s
    end
    
    def burndown_days
        self.release_burndown_days.sort { |a,b| a.day <=> b.day }
    end

    def days(cutoff = nil)
        # assumes mon-fri are working days, sat-sun are not. this
        # assumption is not globally right, we need to make this configurable.
        cutoff = self.release_end_date if cutoff.nil?
        workdays(self.release_start_date, cutoff)
    end

    def has_burndown?
        return !!(self.release_start_date and self.release_end_date and self.initial_story_points)
    end

    def burndown
        return nil if not self.has_burndown?
        @cached_burndown ||= ReleaseBurndown.new(self)
        return @cached_burndown
    end

    def today
      ReleaseBurndownDay.find(:first, :conditions => { :release_id => self, :day => Date.today })
    end

    def js_ideal
      "[['#{release_start_date}', #{initial_story_points}], ['#{release_end_date}', 0]]"
    end

    def js_snapshots
      foo = "["
      if burndown_days and burndown_days[0] and burndown_days[0].day != release_start_date
        foo += "['#{release_start_date}', #{initial_story_points}],"
      end
      burndown_days.each { |bdd| foo += "['#{bdd.day}', #{bdd.remaining_story_points}]," }
      foo += "]"
    end
    
    def estimated_end_date
      burndown_days[-1].day + burndown_days[-1].remaining_story_points / (self.daily_points_burn_rate - self.daily_backlog_grow_rate)
    end

    def risk_factor
      self.daily_points_burn_rate / (self.daily_points_burn_rate - self.daily_backlog_grow_rate)
    end

    def p10_end_date
      burndown_days[-1].day + burndown_days[-1].remaining_story_points / self.daily_points_burn_rate * self.p10_factor + 0.5
    end
    
    def p50_end_date
      burndown_days[-1].day + burndown_days[-1].remaining_story_points / self.daily_points_burn_rate * self.p50_factor + 0.5
    end

    def p90_end_date
      burndown_days[-1].day + burndown_days[-1].remaining_story_points / self.daily_points_burn_rate * self.p90_factor + 0.5
    end
    
end
